//
//  QWNetworkAgent.m
//  Pods
//
//  Created by ZhangYanhua on 15/12/9.
//
//

#import "QWNetworkAgent.h"
#import "QWNetworkConfig.h"
#import "QWNetworkHelpers.h"
#import "AFNetworking.h"

@implementation QWNetworkAgent {
    AFHTTPSessionManager *_manager;
    QWNetworkConfig *_config;
    NSMutableArray *_requestsRecord;
}

+ (QWNetworkAgent *)sharedInstance {
    static id sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

- (id)init {
    self = [super init];
    if (self) {
        _config = [QWNetworkConfig sharedInstance];
        _manager = [AFHTTPSessionManager manager];
        _manager.responseSerializer = [AFHTTPResponseSerializer serializer];
        _requestsRecord = [NSMutableArray array];
        _manager.operationQueue.maxConcurrentOperationCount = 4;
    }
    return self;
}

- (NSString *)buildRequestUrl:(QWNetworkRequest *)request {
    NSString *detailUrl = [request requestUrl];
    if ([detailUrl hasPrefix:@"http"]) {
        return detailUrl;
    }
    // filter url
    NSArray *filters = [_config urlFilters];
    for (id<QWUrlFilterProtocol> f in filters) {
        detailUrl = [f filterUrl:detailUrl withRequest:request];
    }

    NSString *baseUrl;
    if ([request useCDN]) {
        if ([request cdnUrl].length > 0) {
            baseUrl = [request cdnUrl];
        } else {
            baseUrl = [_config cdnUrl];
        }
    } else {
        if ([request baseUrl].length > 0) {
            baseUrl = [request baseUrl];
        } else {
            baseUrl = [_config baseUrl];
        }
    }
    return [baseUrl stringByAppendingPathComponent:detailUrl];
}

- (void)addRequest:(QWNetworkRequest *)request {
    QWRequestMethod method = [request requestMethod];
    NSString *url = [self buildRequestUrl:request];
    id param = request.requestArgument;
    AFConstructingBlock constructingBlock = [request constructingBodyBlock];
    
    if (request.requestSerializerType == QWRequestSerializerTypeHTTP) {
        _manager.requestSerializer = [AFHTTPRequestSerializer serializer];
    } else if (request.requestSerializerType == QWRequestSerializerTypeJSON) {
        _manager.requestSerializer = [AFJSONRequestSerializer serializer];
    }
    
    _manager.requestSerializer.timeoutInterval = [request requestTimeoutInterval];
    
    // if api need server username and password
    NSArray *authorizationHeaderFieldArray = [request requestAuthorizationHeaderFieldArray];
    if (authorizationHeaderFieldArray != nil) {
        [_manager.requestSerializer setAuthorizationHeaderFieldWithUsername:(NSString *)authorizationHeaderFieldArray.firstObject
                                                                   password:(NSString *)authorizationHeaderFieldArray.lastObject];
    }
    
    // if api need add custom value to HTTPHeaderField
    NSDictionary *headerFieldValueDictionary = [request requestHeaderFieldValueDictionary];
    if (headerFieldValueDictionary != nil) {
        for (id httpHeaderField in headerFieldValueDictionary.allKeys) {
            id value = headerFieldValueDictionary[httpHeaderField];
            if ([httpHeaderField isKindOfClass:[NSString class]] && [value isKindOfClass:[NSString class]]) {
                [_manager.requestSerializer setValue:(NSString *)value forHTTPHeaderField:(NSString *)httpHeaderField];
            } else {
                QWLog(@"Error, class of key/value in headerFieldValueDictionary should be NSString.");
            }
        }
    }

    if (method == QWRequestMethodGet) {
        if (request.resumableDownloadPath) {
            // add parameters to URL;
            NSString *filteredUrl = [QWNetworkHelpers urlStringWithOriginUrlString:url appendParameters:param];
            
            NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
            AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
            
            NSURLRequest *requestUrl = [NSURLRequest requestWithURL:[NSURL URLWithString:filteredUrl]];
            
            request.sessionTask = [manager downloadTaskWithRequest:requestUrl
                                                          progress:^(NSProgress * _Nonnull downloadProgress) {
                                                              request.resumableDownloadProgressBlock(request, downloadProgress);
                                                          }
                                                       destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
                                                           NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
                                                           return [documentsDirectoryURL URLByAppendingPathComponent:request.resumableDownloadPath];
                                                       } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
                                                            [self parseResultForRequest:request
                                                                       withResponseData:filePath
                                                                                  error:error];
                                                       }];
            [request.sessionTask resume];
        } else {
            request.sessionTask = [_manager GET:url
                                     parameters:param
                                       progress:nil
                                        success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                                            [self parseResultForRequest:request
                                                       withResponseData:responseObject
                                                                  error:nil];
                                        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                                            [self parseResultForRequest:request
                                                       withResponseData:nil
                                                                  error:error];
                                        }];
        }
    } else if (method == QWRequestMethodPost) {
        if (constructingBlock != nil) {
            request.sessionTask = [_manager POST:url
                                      parameters:param
                       constructingBodyWithBlock:constructingBlock
                                        progress:nil
                                         success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                                             [self parseResultForRequest:request
                                                        withResponseData:responseObject
                                                                   error:nil];
                                         } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                                             [self parseResultForRequest:request
                                                        withResponseData:nil
                                                                   error:error];
                                         }];
        } else {
            request.sessionTask = [_manager POST:url
                                      parameters:param
                                        progress:nil
                                         success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                                             [self parseResultForRequest:request
                                                        withResponseData:responseObject
                                                                   error:nil];
                                         } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                                             [self parseResultForRequest:request
                                                        withResponseData:nil
                                                                   error:error];
                                         }];
        }
    } else {
        QWLog(@"Error, unsupport method type");
        return;
    }
    
    QWLog(@"Add request: %@", NSStringFromClass([request class]));
    [self addOperation:request];
}

- (void)cancelRequest:(QWNetworkRequest *)request {
    [request.sessionTask cancel];
    [self removeOperation:request];
    [request clearCompletionBlock];
}

- (void)cancelAllRequests {
    NSArray *copyRecord = [_requestsRecord copy];
    for (QWNetworkRequest *request in copyRecord) {
        [request stop];
    }
}

- (void)parseResultForRequest:(QWNetworkRequest *)request
             withResponseData:(id)responseData
                        error:(NSError *)error
{
    QWLog(@"Finished Request: %@", NSStringFromClass([request class]));
    if (request) {
        BOOL isSuccess = [request handleResultWithResonseData:responseData error:error];

        if (isSuccess) {
            [request requestCompleteFilterWithResponseData:responseData];
            if (request.successCompletionBlock) {
                request.successCompletionBlock(request);
            }
        } else {
            QWLog(@"Request %@ failed", NSStringFromClass([request class]));
            [request requestFailedFilterWithError:error];
            if (request.failureCompletionBlock) {
                request.failureCompletionBlock(request);
            }
        }
    }
    [self removeOperation:request];
    [request clearCompletionBlock];
}

- (void)addOperation:(QWNetworkRequest *)request {
    if (request.sessionTask != nil) {
        @synchronized(self) {
            [_requestsRecord addObject:request];
        }
    }
}

- (void)removeOperation:(QWNetworkRequest *)request {
    @synchronized(self) {
        [_requestsRecord removeObject:request];
    }
    QWLog(@"Request queue size = %lu", (unsigned long)[_requestsRecord count]);
}

@end
